/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.nio.charset;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;

/**
 * A converter that can convert a byte sequence from a charset into a 16-bit
 * Unicode character sequence.
 * <p>
 * The input byte sequence is wrapped by a
 * {@link java.nio.ByteBuffer ByteBuffer} and the output character sequence is a
 * {@link java.nio.CharBuffer CharBuffer}. A decoder instance should be used in
 * the following sequence, which is referred to as a decoding operation:
 * <ol>
 * <li>invoking the {@link #reset() reset} method to reset the decoder if the
 * decoder has been used;</li>
 * <li>invoking the {@link #decode(ByteBuffer, CharBuffer, boolean) decode}
 * method until the additional input is not needed, the <code>endOfInput</code>
 * parameter must be set to false, the input buffer must be filled and the
 * output buffer must be flushed between invocations;</li>
 * <li>invoking the {@link #decode(ByteBuffer, CharBuffer, boolean) decode}
 * method for the last time, and then the <code>endOfInput</code> parameter
 * must be set to true;</li>
 * <li>invoking the {@link #flush(CharBuffer) flush} method to flush the
 * output.</li>
 * </ol>
 * <p>
 * The {@link #decode(ByteBuffer, CharBuffer, boolean) decode} method will
 * convert as many bytes as possible, and the process won't stop until the input
 * bytes have run out, the output buffer has been filled or some error has
 * happened. A {@link CoderResult CoderResult} instance will be returned to
 * indicate the stop reason, and the invoker can identify the result and choose
 * further action, which includes filling the input buffer, flushing the output
 * buffer or recovering from an error and trying again.
 * <p>
 * There are two common decoding errors. One is named malformed and it is
 * returned when the input byte sequence is illegal for the current specific
 * charset, the other is named unmappable character and it is returned when a
 * problem occurs mapping a legal input byte sequence to its Unicode character
 * equivalent.
 * <p>
 * Both errors can be handled in three ways, the default one is to report the
 * error to the invoker by a {@link CoderResult CoderResult} instance, and the
 * alternatives are to ignore it or to replace the erroneous input with the
 * replacement string. The replacement string is "\uFFFD" by default and can be
 * changed by invoking {@link #replaceWith(String) replaceWith} method. The
 * invoker of this decoder can choose one way by specifying a
 * {@link CodingErrorAction CodingErrorAction} instance for each error type via
 * {@link #onMalformedInput(CodingErrorAction) onMalformedInput} method and
 * {@link #onUnmappableCharacter(CodingErrorAction) onUnmappableCharacter}
 * method.
 * <p>
 * This is an abstract class and encapsulates many common operations of the
 * decoding process for all charsets. Decoders for a specific charset should
 * extend this class and need only to implement the
 * {@link #decodeLoop(ByteBuffer, CharBuffer) decodeLoop} method for the basic
 * decoding. If a subclass maintains an internal state, it should override the
 * {@link #implFlush(CharBuffer) implFlush} method and the
 * {@link #implReset() implReset} method in addition.
 * <p>
 * This class is not thread-safe.
 *
 * @see java.nio.charset.Charset
 * @see java.nio.charset.CharsetEncoder
 */
public abstract class CharsetDecoder {
	private static final int RESET = 0;
	private static final int ONGOING = 1;
	private static final int END_OF_INPUT = 2;
	private static final int FLUSHED = 3;

	private final Charset charset;

	private final float averageCharsPerByte;
	private final float maxCharsPerByte;

	private String replacementChars = "\ufffd";

	private int state = RESET;

	private CodingErrorAction malformedInputAction = CodingErrorAction.REPORT;
	private CodingErrorAction unmappableCharacterAction = CodingErrorAction.REPORT;

	/**
	 * Constructs a new <code>CharsetDecoder</code> using the given
	 * <code>Charset</code>, average number and maximum number of characters
	 * created by this decoder for one input byte, and the default replacement
	 * string "\uFFFD".
	 *
	 * @param charset
	 *            the <code>Charset</code> to be used by this decoder.
	 * @param averageCharsPerByte
	 *            the average number of characters created by this decoder for
	 *            one input byte, must be positive.
	 * @param maxCharsPerByte
	 *            the maximum number of characters created by this decoder for
	 *            one input byte, must be positive.
	 * @throws IllegalArgumentException
	 *     if {@code averageCharsPerByte <= 0 || maxCharsPerByte <= 0 || averageCharsPerByte > maxCharsPerByte}.
	 */
	protected CharsetDecoder(Charset charset, float averageCharsPerByte, float maxCharsPerByte) {
		if (averageCharsPerByte <= 0 || maxCharsPerByte <= 0) {
			throw new IllegalArgumentException("averageCharsPerByte and maxCharsPerByte must be positive");
		}
		if (averageCharsPerByte > maxCharsPerByte) {
			throw new IllegalArgumentException("averageCharsPerByte is greater than maxCharsPerByte");
		}
		this.averageCharsPerByte = averageCharsPerByte;
		this.maxCharsPerByte = maxCharsPerByte;
		this.charset = charset;
	}

	/**
	 * Returns the average number of characters created by this decoder for a
	 * single input byte.
	 */
	public final float averageCharsPerByte() {
		return averageCharsPerByte;
	}

	/**
	 * Returns the {@link Charset} which this decoder uses.
	 */
	public final Charset charset() {
		return charset;
	}

	/**
	 * This is a facade method for the decoding operation.
	 * <p>
	 * This method decodes the remaining byte sequence of the given byte buffer
	 * into a new character buffer. This method performs a complete decoding
	 * operation, resets at first, then decodes, and flushes at last.
	 * <p>
	 * This method should not be invoked while another {@code decode} operation
	 * is ongoing.
	 *
	 * @param in
	 *            the input buffer.
	 * @return a new <code>CharBuffer</code> containing the the characters
	 *         produced by this decoding operation. The buffer's limit will be
	 *         the position of the last character in the buffer, and the
	 *         position will be zero.
	 * @throws IllegalStateException
	 *             if another decoding operation is ongoing.
	 * @throws MalformedInputException
	 *             if an illegal input byte sequence for this charset was
	 *             encountered, and the action for malformed error is
	 *             {@link CodingErrorAction#REPORT CodingErrorAction.REPORT}
	 * @throws UnmappableCharacterException
	 *             if a legal but unmappable input byte sequence for this
	 *             charset was encountered, and the action for unmappable
	 *             character error is
	 *             {@link CodingErrorAction#REPORT CodingErrorAction.REPORT}.
	 *             Unmappable means the byte sequence at the input buffer's
	 *             current position cannot be mapped to a Unicode character
	 *             sequence.
	 * @throws CharacterCodingException
	 *             if another exception happened during the decode operation.
	 */
	public final CharBuffer decode(ByteBuffer in) throws CharacterCodingException {
		int length = (int) (in.remaining() * averageCharsPerByte);
		CharBuffer out = CharBuffer.allocate(length);

		reset();

		while (state != FLUSHED) {
			CoderResult result = decode(in, out, true);
			if (result == CoderResult.OVERFLOW) {
				out = allocateMore(out);
				continue; // No point trying to flush to an already-full buffer.
			} else {
				checkCoderResult(result);
			}

			result = flush(out);
			if (result == CoderResult.OVERFLOW) {
				out = allocateMore(out);
			} else {
				checkCoderResult(result);
			}
		}

		out.flip();
		return out;
	}

	/*
     * checks the result whether it needs to throw CharacterCodingException.
     */
	private void checkCoderResult(CoderResult result) throws CharacterCodingException {
		if (result.isMalformed() && malformedInputAction == CodingErrorAction.REPORT) {
			throw new MalformedInputException(result.length());
		} else if (result.isUnmappable() && unmappableCharacterAction == CodingErrorAction.REPORT) {
			throw new UnmappableCharacterException(result.length());
		}
	}

	/*
     * original output is full and doesn't have remaining. allocate more space
     * to new CharBuffer and return it, the contents in the given buffer will be
     * copied into the new buffer.
     */
	private CharBuffer allocateMore(CharBuffer output) {
		if (output.capacity() == 0) {
			return CharBuffer.allocate(1);
		}
		CharBuffer result = CharBuffer.allocate(output.capacity() * 2);
		output.flip();
		result.put(output);
		return result;
	}

	/**
	 * Decodes bytes starting at the current position of the given input buffer,
	 * and writes the equivalent character sequence into the given output buffer
	 * from its current position.
	 * <p>
	 * The buffers' position will be changed with the reading and writing
	 * operation, but their limits and marks will be kept intact.
	 * <p>
	 * A <code>CoderResult</code> instance will be returned according to
	 * following rules:
	 * <ul>
	 * <li>{@link CoderResult#OVERFLOW CoderResult.OVERFLOW} indicates that
	 * even though not all of the input has been processed, the buffer the
	 * output is being written to has reached its capacity. In the event of this
	 * code being returned this method should be called once more with an
	 * <code>out</code> argument that has not already been filled.</li>
	 * <li>{@link CoderResult#UNDERFLOW CoderResult.UNDERFLOW} indicates that
	 * as many bytes as possible in the input buffer have been decoded. If there
	 * is no further input and no remaining bytes in the input buffer then this
	 * operation may be regarded as complete. Otherwise, this method should be
	 * called once more with additional input.</li>
	 * <li>A {@link CoderResult#malformedForLength(int) malformed input} result
	 * indicates that some malformed input error has been encountered, and the
	 * erroneous bytes start at the input buffer's position and their number can
	 * be got by result's {@link CoderResult#length() length}. This kind of
	 * result can be returned only if the malformed action is
	 * {@link CodingErrorAction#REPORT CodingErrorAction.REPORT}. </li>
	 * <li>A {@link CoderResult#unmappableForLength(int) unmappable character}
	 * result indicates that some unmappable character error has been
	 * encountered, and the erroneous bytes start at the input buffer's position
	 * and their number can be got by result's
	 * {@link CoderResult#length() length}. This kind of result can be returned
	 * only if the unmappable character action is
	 * {@link CodingErrorAction#REPORT CodingErrorAction.REPORT}. </li>
	 * </ul>
	 * <p>
	 * The <code>endOfInput</code> parameter indicates that the invoker cannot
	 * provide further input. This parameter is true if and only if the bytes in
	 * current input buffer are all inputs for this decoding operation. Note
	 * that it is common and won't cause an error if the invoker sets false and
	 * then can't provide more input, while it may cause an error if the invoker
	 * always sets true in several consecutive invocations. This would make the
	 * remaining input to be treated as malformed input.
	 * <p>
	 * This method invokes the
	 * {@link #decodeLoop(ByteBuffer, CharBuffer) decodeLoop} method to
	 * implement the basic decode logic for a specific charset.
	 *
	 * @param in
	 *            the input buffer.
	 * @param out
	 *            the output buffer.
	 * @param endOfInput
	 *            true if all the input characters have been provided.
	 * @return a <code>CoderResult</code> instance which indicates the reason
	 *         of termination.
	 * @throws IllegalStateException
	 *             if decoding has started or no more input is needed in this
	 *             decoding progress.
	 * @throws CoderMalfunctionError
	 *             if the {@link #decodeLoop(ByteBuffer, CharBuffer) decodeLoop}
	 *             method threw an <code>BufferUnderflowException</code> or
	 *             <code>BufferOverflowException</code>.
	 */
	public final CoderResult decode(ByteBuffer in, CharBuffer out, boolean endOfInput) {
		if (state != RESET && state != ONGOING && !(endOfInput && state == END_OF_INPUT)) {
			throw illegalStateException();
		}

		state = endOfInput ? END_OF_INPUT : ONGOING;

		while (true) {
			CoderResult result;
			try {
				result = decodeLoop(in, out);
			} catch (BufferOverflowException ex) {
				throw new CoderMalfunctionError(ex);
			} catch (BufferUnderflowException ex) {
				throw new CoderMalfunctionError(ex);
			}

			if (result == CoderResult.UNDERFLOW) {
				if (endOfInput && in.hasRemaining()) {
					result = CoderResult.malformedForLength(in.remaining());
				} else {
					return result;
				}
			} else if (result == CoderResult.OVERFLOW) {
				return result;
			}

			// We have a real error, so do what the appropriate action tells us what to do...
			CodingErrorAction action =
				result.isUnmappable() ? unmappableCharacterAction : malformedInputAction;
			if (action == CodingErrorAction.REPORT) {
				return result;
			} else if (action == CodingErrorAction.REPLACE) {
				if (out.remaining() < replacementChars.length()) {
					return CoderResult.OVERFLOW;
				}
				out.put(replacementChars);
			}
			in.position(in.position() + result.length());
		}
	}

	/**
	 * Decodes bytes into characters. This method is called by the
	 * {@link #decode(ByteBuffer, CharBuffer, boolean) decode} method.
	 * <p>
	 * This method will implement the essential decoding operation, and it won't
	 * stop decoding until either all the input bytes are read, the output
	 * buffer is filled, or some exception is encountered. Then it will return a
	 * <code>CoderResult</code> object indicating the result of current
	 * decoding operation. The rules to construct the <code>CoderResult</code>
	 * are the same as for
	 * {@link #decode(ByteBuffer, CharBuffer, boolean) decode}. When an
	 * exception is encountered in the decoding operation, most implementations
	 * of this method will return a relevant result object to the
	 * {@link #decode(ByteBuffer, CharBuffer, boolean) decode} method, and some
	 * performance optimized implementation may handle the exception and
	 * implement the error action itself.
	 * <p>
	 * The buffers are scanned from their current positions, and their positions
	 * will be modified accordingly, while their marks and limits will be
	 * intact. At most {@link ByteBuffer#remaining() in.remaining()} characters
	 * will be read, and {@link CharBuffer#remaining() out.remaining()} bytes
	 * will be written.
	 * <p>
	 * Note that some implementations may pre-scan the input buffer and return a
	 * <code>CoderResult.UNDERFLOW</code> until it receives sufficient input.
	 *
	 * @param in
	 *            the input buffer.
	 * @param out
	 *            the output buffer.
	 * @return a <code>CoderResult</code> instance indicating the result.
	 */
	protected abstract CoderResult decodeLoop(ByteBuffer in, CharBuffer out);

	/**
	 * Gets the charset detected by this decoder; this method is optional.
	 * <p>
	 * If implementing an auto-detecting charset, then this decoder returns the
	 * detected charset from this method when it is available. The returned
	 * charset will be the same for the rest of the decode operation.
	 * <p>
	 * If insufficient bytes have been read to determine the charset, an
	 * <code>IllegalStateException</code> will be thrown.
	 * <p>
	 * The default implementation always throws
	 * <code>UnsupportedOperationException</code>, so it should be overridden
	 * by a subclass if needed.
	 *
	 * @return the charset detected by this decoder, or null if it is not yet
	 *         determined.
	 * @throws UnsupportedOperationException
	 *             if this decoder does not implement an auto-detecting charset.
	 * @throws IllegalStateException
	 *             if insufficient bytes have been read to determine the
	 *             charset.
	 */
	public Charset detectedCharset() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Flushes this decoder.
	 *
	 * This method will call {@link #implFlush(CharBuffer) implFlush}. Some
	 * decoders may need to write some characters to the output buffer when they
	 * have read all input bytes; subclasses can override
	 * {@link #implFlush(CharBuffer) implFlush} to perform the writing operation.
	 * <p>
	 * The maximum number of written bytes won't be larger than
	 * {@link CharBuffer#remaining() out.remaining()}. If some decoder wants to
	 * write more bytes than an output buffer's remaining space allows, then a
	 * <code>CoderResult.OVERFLOW</code> will be returned, and this method
	 * must be called again with a character buffer that has more remaining
	 * space. Otherwise this method will return
	 * <code>CoderResult.UNDERFLOW</code>, which means one decoding process
	 * has been completed successfully.
	 * <p>
	 * During the flush, the output buffer's position will be changed
	 * accordingly, while its mark and limit will be intact.
	 *
	 * @param out
	 *            the given output buffer.
	 * @return <code>CoderResult.UNDERFLOW</code> or
	 *         <code>CoderResult.OVERFLOW</code>.
	 * @throws IllegalStateException
	 *             if this decoder isn't already flushed or at end of input.
	 */
	public final CoderResult flush(CharBuffer out) {
		if (state != FLUSHED && state != END_OF_INPUT) {
			throw illegalStateException();
		}
		CoderResult result = implFlush(out);
		if (result == CoderResult.UNDERFLOW) {
			state = FLUSHED;
		}
		return result;
	}

	/**
	 * Flushes this decoder. The default implementation does nothing and always
	 * returns <code>CoderResult.UNDERFLOW</code>; this method can be
	 * overridden if needed.
	 *
	 * @param out
	 *            the output buffer.
	 * @return <code>CoderResult.UNDERFLOW</code> or
	 *         <code>CoderResult.OVERFLOW</code>.
	 */
	protected CoderResult implFlush(CharBuffer out) {
		return CoderResult.UNDERFLOW;
	}

	/**
	 * Notifies that this decoder's <code>CodingErrorAction</code> specified
	 * for malformed input error has been changed. The default implementation
	 * does nothing; this method can be overridden if needed.
	 *
	 * @param newAction
	 *            the new action.
	 */
	protected void implOnMalformedInput(CodingErrorAction newAction) {
		// default implementation is empty
	}

	/**
	 * Notifies that this decoder's <code>CodingErrorAction</code> specified
	 * for unmappable character error has been changed. The default
	 * implementation does nothing; this method can be overridden if needed.
	 *
	 * @param newAction
	 *            the new action.
	 */
	protected void implOnUnmappableCharacter(CodingErrorAction newAction) {
		// default implementation is empty
	}

	/**
	 * Notifies that this decoder's replacement has been changed. The default
	 * implementation does nothing; this method can be overridden if needed.
	 *
	 * @param newReplacement
	 *            the new replacement string.
	 */
	protected void implReplaceWith(String newReplacement) {
		// default implementation is empty
	}

	/**
	 * Reset this decoder's charset related state. The default implementation
	 * does nothing; this method can be overridden if needed.
	 */
	protected void implReset() {
		// default implementation is empty
	}

	/**
	 * Indicates whether this decoder implements an auto-detecting charset.
	 *
	 * @return <code>true</code> if this decoder implements an auto-detecting
	 *         charset.
	 */
	public boolean isAutoDetecting() {
		return false;
	}

	/**
	 * Indicates whether this decoder has detected a charset; this method is
	 * optional.
	 * <p>
	 * If this decoder implements an auto-detecting charset, then this method
	 * may start to return true during decoding operation to indicate that a
	 * charset has been detected in the input bytes and that the charset can be
	 * retrieved by invoking the {@link #detectedCharset() detectedCharset}
	 * method.
	 * <p>
	 * Note that a decoder that implements an auto-detecting charset may still
	 * succeed in decoding a portion of the given input even when it is unable
	 * to detect the charset. For this reason users should be aware that a
	 * <code>false</code> return value does not indicate that no decoding took
	 * place.
	 * <p>
	 * The default implementation always throws an
	 * <code>UnsupportedOperationException</code>; it should be overridden by
	 * a subclass if needed.
	 *
	 * @return <code>true</code> if this decoder has detected a charset.
	 * @throws UnsupportedOperationException
	 *             if this decoder doesn't implement an auto-detecting charset.
	 */
	public boolean isCharsetDetected() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns this decoder's <code>CodingErrorAction</code> when malformed input
	 * occurred during the decoding process.
	 */
	public CodingErrorAction malformedInputAction() {
		return malformedInputAction;
	}

	/**
	 * Returns the maximum number of characters which can be created by this
	 * decoder for one input byte, must be positive.
	 */
	public final float maxCharsPerByte() {
		return maxCharsPerByte;
	}

	/**
	 * Sets this decoder's action on malformed input errors.
	 *
	 * This method will call the
	 * {@link #implOnMalformedInput(CodingErrorAction) implOnMalformedInput}
	 * method with the given new action as argument.
	 *
	 * @param newAction
	 *            the new action on malformed input error.
	 * @return this decoder.
	 * @throws IllegalArgumentException
	 *             if {@code newAction == null}.
	 */
	public final CharsetDecoder onMalformedInput(CodingErrorAction newAction) {
		if (newAction == null) {
			throw new IllegalArgumentException("newAction == null");
		}
		malformedInputAction = newAction;
		implOnMalformedInput(newAction);
		return this;
	}

	/**
	 * Sets this decoder's action on unmappable character errors.
	 *
	 * This method will call the
	 * {@link #implOnUnmappableCharacter(CodingErrorAction) implOnUnmappableCharacter}
	 * method with the given new action as argument.
	 *
	 * @param newAction
	 *            the new action on unmappable character error.
	 * @return this decoder.
	 * @throws IllegalArgumentException
	 *             if {@code newAction == null}.
	 */
	public final CharsetDecoder onUnmappableCharacter(CodingErrorAction newAction) {
		if (newAction == null) {
			throw new IllegalArgumentException("newAction == null");
		}
		unmappableCharacterAction = newAction;
		implOnUnmappableCharacter(newAction);
		return this;
	}

	/**
	 * Returns the replacement string, which is never null or empty.
	 */
	public final String replacement() {
		return replacementChars;
	}

	/**
	 * Sets the new replacement string.
	 *
	 * This method first checks the given replacement's validity, then changes
	 * the replacement value, and at last calls the
	 * {@link #implReplaceWith(String) implReplaceWith} method with the given
	 * new replacement as argument.
	 *
	 * @param replacement
	 *            the replacement string cannot be null, empty, or longer
	 *            than {@link #maxCharsPerByte()}.
	 * @return this decoder.
	 * @throws IllegalArgumentException
	 *             if the given replacement cannot satisfy the requirement
	 *             mentioned above.
	 */
	public final CharsetDecoder replaceWith(String replacement) {
		if (replacement == null) {
			throw new IllegalArgumentException("replacement == null");
		}
		if (replacement.isEmpty()) {
			throw new IllegalArgumentException("replacement.isEmpty()");
		}
		if (replacement.length() > maxCharsPerByte()) {
			throw new IllegalArgumentException("replacement length > maxCharsPerByte: " +
				replacement.length() + " > " + maxCharsPerByte());
		}
		replacementChars = replacement;
		implReplaceWith(replacement);
		return this;
	}

	/**
	 * Resets this decoder. This method will reset the internal state, and then
	 * calls {@link #implReset} to reset any state related to the
	 * specific charset.
	 */
	public final CharsetDecoder reset() {
		state = RESET;
		implReset();
		return this;
	}

	/**
	 * Returns this decoder's <code>CodingErrorAction</code> when an unmappable
	 * character error occurred during the decoding process.
	 */
	public CodingErrorAction unmappableCharacterAction() {
		return unmappableCharacterAction;
	}

	private IllegalStateException illegalStateException() {
		throw new IllegalStateException("State: " + state);
	}
}